﻿// -----------------------------------------------------------------------
// <copyright file="DocumentScanner.cs" company="Open Box Software">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------

namespace SSMSProcedureList
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using System.Text.RegularExpressions;


    public class TextComparison : IComparer<TextContext>
    {
        #region IComparer<TextContext> Members

        public int Compare(TextContext x, TextContext y)
        {
            return string.Compare(x.Text, y.Text);
        }

        #endregion
    }

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    public class DocumentScanner
    {
        List<int> _absoluteCharToLineMap;
        string _text;
        string _absoluteText;
        public DocumentScanner(string text)
        {
            this._text = text;
            this._absoluteText = text;
            this._absoluteCharToLineMap = null;
        }


        int MapCharIndexToLineNumber(int index)
        {
            if (this._absoluteCharToLineMap == null)
            {
                this._absoluteCharToLineMap = new List<int>();
                foreach (var line in this.GetLines())
                {
                    var lineLen = line.Length;
                    lineLen += 2;
                    this._absoluteCharToLineMap.Add(lineLen);
                }
            }

            var idx = 0;
            var lineNumber = 1;
            foreach (var len in this._absoluteCharToLineMap)
            {
                idx += len;
                if (idx >= index)
                {
                    return lineNumber;
                }
                lineNumber += 1;
            }

            return -1;
        }

        IEnumerable<TextContext> GetFirstDistinctLines(IEnumerable<TextContext> ctxList)
        {
            var lookup = new Dictionary<string, TextContext>();
            foreach (TextContext ctx in ctxList)
            {
                if (!lookup.ContainsKey(ctx.Text.ToUpper()))
                {
                    lookup[ctx.Text.ToUpper()] = ctx;
                    yield return ctx;
                }
            }
        }

        IEnumerable<TextContext> SortList(IEnumerable<TextContext> list)
        {
            var sortedList = new List<TextContext>();
            foreach (var ctx in list)
            {
                sortedList.Add(ctx);
            }
            sortedList.Sort(new TextComparison());
            foreach (var ctx in sortedList)
            {
                yield return ctx;
            }
        }

        Func<string, Match, string> GetEvaluatorFirstGroup()
        {
            return (text, match) => match.Groups[1].ToString();
        }


        IEnumerable<TextContext> MatchLinesSorted(string expr, Func<string, Match, string> matchEvaluator)
        {
            foreach (var ctx in this.SortList(this.MatchLines(expr, matchEvaluator)))
            {
                yield return ctx;
            }
        }

        IEnumerable<TextContext> MatchLinesSortedDefault(string expr)
        {
            return this.MatchLinesSorted(expr, this.GetEvaluatorFirstGroup());
        }

        IEnumerable<TextContext> GetFirstDistinctLinesSortedDefault(string expr)
        {
            foreach (var table in this.GetFirstDistinctLinesSorted(expr, this.GetEvaluatorFirstGroup()))
            {
                yield return table;
            }
        }

        public IEnumerable<TextContext> GetRegions()
        {
            return this.GetFirstDistinctLinesSorted(@"--[\s]+#region[\s]+?([\w\s]+)", (text, match) => match.Groups[1].ToString());
        }

        public IEnumerable<TextContext> GetVariables()
        {
            return this.GetFirstDistinctLinesSorted(@"DECLARE[\s]+([@\w]+)", this.GetEvaluatorFirstGroup());
        }

        public IEnumerable<TextContext> GetIndexes()
        {
            return this.GetFirstDistinctLinesSorted(@"CREATE[\s\w]+INDEX[\s]+([\w]+)[\s]+ON[\s]+([#\w]+)",
                (text, match) => string.Format("{0}.{1}", match.Groups[2].ToString(), match.Groups[1].ToString()));
        }

        public IEnumerable<string> GetLines()
        {
            var ms = new MemoryStream(Encoding.UTF8.GetBytes(this._text));
            using (var sr = new StreamReader(ms))
            {
                while (!sr.EndOfStream)
                {
                    yield return sr.ReadLine();
                }
            }
        }

        IEnumerable<TextContext> GetFirstDistinctLinesSorted(string expr, Func<string, Match, string> matchEvaluator)
        {
            foreach (var ctx in this.SortList(this.GetFirstDistinctLines(this.MatchLines(expr, matchEvaluator))))
            {
                yield return ctx;
            }
        }

        IEnumerable<TextContext> MatchLines(string expr, Func<string, Match, string> matchEvaluator)
        {
            if (matchEvaluator == null)
            {
                matchEvaluator = (text, match) => text.Substring(match.Index, match.Index + match.Length);
            }

            var options = RegexOptions.IgnoreCase;
            options |= RegexOptions.Singleline;
            var tempExpr = new Regex(expr, options);
            var tempLineNumber = 1;

            foreach (var line in this.GetLines())
            {
                var m = tempExpr.Match(line);
                if (m.Success)
                {
                    var textPortion = matchEvaluator(line, m);
                    var ctx = new TextContext(textPortion, tempLineNumber);
                    yield return ctx;
                }
                tempLineNumber += 1;
            }
        }

        IEnumerable<TextContext> MatchAbsolute(string expr, Func<string, Match, string> matchEvaluator, Func<Match, int> indexEvaluator = null)
        {
            if (matchEvaluator == null)
            {
                matchEvaluator = (text, match) => text.Substring(match.Index, match.Index + match.Length);
            }
            if (indexEvaluator == null)
            {
                indexEvaluator = (match) => match.Groups[1].Index;
            }

            var options = RegexOptions.IgnoreCase;
            options |= RegexOptions.Singleline;
            var tempExpr = new Regex(expr, options);
            var tempLineNumber = 1;
            var m = tempExpr.Match(this._absoluteText);
            while (m.Success)
            {
                var textPortion = matchEvaluator(this._absoluteText, m);
                var ctx = new TextContext(textPortion, this.MapCharIndexToLineNumber(indexEvaluator(m)));
                yield return ctx;
                m = m.NextMatch();
            }
        }


        public IEnumerable<TextContext> GetTempTables()
        {
            var tables = new List<TextContext>();


            foreach (var ctx in this.GetFirstDistinctLinesSorted(@"CREATE TABLE[\s]+?([#\w\[\]]+)", this.GetEvaluatorFirstGroup()))
            {
                tables.Add(ctx);
            }

            foreach (var ctx in this.SortList(this.GetFirstDistinctLines(this.MatchAbsolute(@"SELECT.*?INTO.*?([#\w]+).*?FROM", this.GetEvaluatorFirstGroup()))))
            {
                tables.Add(ctx);
            }

            var ctxList = this.GetFirstDistinctLines(tables);
            return this.SortList(ctxList);
        }

    }



    public class TextContext
    {
        public TextContext(string text, int lineNumber, bool isAbsolute)
        {
            this.Text = text;
            this.LineNumber = lineNumber;
            this.IsAbsolute = isAbsolute;

        }

        public TextContext(string text, int lineNumber)
            : this(text, lineNumber, false)
        {

        }

        public string Text { get; set; }
        public int LineNumber { get; set; }
        public bool IsAbsolute { get; set; }
    }



}
